#import "FortuneView.h"
#import <math.h>
#import <ctype.h>
#import "Thinker.h"

// this code is copyright Darcy Brockbank, 1994
//
// You may freely reuse and distribute this code in any way shape or
// form, provided that this notice stays intact.
//
// darcy@hasc.ca, samurai@cs.mcgill.ca
//
// If you do improve this thing, send me a copy!
//
// - darcy

#define INCLUDED (char)0
#define EXCLUDED (char)1

#define SHADOW 0
#define HIGHLIGHT 1
#define PLAIN 2

#define SPEED		"FortuneSpeed"
#define USECOLOR	"FortuneUseColor"
#define F_FILE		"FortuneFile"
#define BACKING		"FortuneBacking"
#define CLEARAFTER	"FortuneClearAfter"
#define EXFONTS		"FortuneExcludedFonts"

#define ROT(c) ((isupper(c))? ('A' + (c - 'A' + 13) % 26) : \
			((islower(c)) ? ('a' + (c - 'a' + 13) % 26) : c))

@interface ThreeField : TextField
{
	int drawBacking;
}

- setDrawBacking:(int)flag;
- drawSelf:(const NXRect *)r :(int)c;

@end

@implementation ThreeField

- setDrawBacking:(int)flag;
{
	drawBacking = flag;
	return self;
}


- drawSelf:(const NXRect *)r :(int)c;
{
	NXColor realColor = [self textColor];
	NXRect hoffset=*r;
	if (strlen([self stringValue])==0){
		NXSetColor(NX_COLORBLACK);
		NXRectFill(r);
	} else if (drawBacking==HIGHLIGHT){
		hoffset.origin.x+=1.0;
		hoffset.origin.y+=1.0;
		[cell setTextColor:NX_COLORWHITE];
		[cell drawInside:&hoffset inView:self];
		[cell setTextColor:realColor];
		[cell drawInside:r inView:self];
	} else {
		[cell drawInside:r inView:self];
	}
	return self;
}

@end

@interface GrayCell : NXBrowserCell
{
	BOOL isGray;
}
- setGrayed:(BOOL)flag;
@end

@implementation GrayCell
- setGrayed:(BOOL)flag;
{
	isGray = flag;
	return self;
}

- setTextAttributes:sender;
{
	[super setTextAttributes:sender];
	if (isGray) [sender setTextGray:NX_DKGRAY];
	return self;
}

@end

@implementation FortuneView

#define MINX 0.0
#define MINY 0.0
#define SIZE 36.0

static const char * const fortunes[] = {
#define NORMAL 0
	"fortunes",
#define STARTREK 1
	"startrek",
#define OFFENSIVE 2
	"fortunes-o",
#define ZIPPY 3
	"zippy",
	0
};


- newDatabase:sender;
{
	int sel = [[sender selectedCell] tag];
	if (sel == OFFENSIVE) {
		if (NXRunAlertPanel("Be sure...", "This option will print fortune entries on the screen "
				"which some people will find offensive (hence the name). If you "
				"are offended by offcolor jokes, or sexually explicit remarks, "
				"or are easily offended at all, then I suggest you not use this option. "
				"The fact that these fortunes are included as part of this screen "
				"saver does not imply that I condone or agree with any of them. "
				"They are included since they were part of the Berkeley 'fortune' package.",
				"Continue","Cancel Selection",0)==NX_ALERTALTERNATE)
		{
			[offensiveMatrix perform:@selector(selectCellWithTag:)
					with:(id)fortuneFile
					afterDelay:1
					cancelPrevious:YES];
			return self;
		}
	}
	fortuneFile = sel;
	[self loadWords:fortunes[fortuneFile]];
	[self perform:@selector(writeFileDefault:)
		with:self
		afterDelay:1000
		cancelPrevious:YES];
	[self lockFocus];
	[self doDrawing];
	[self unlockFocus];
	return self;
}



- writeThresholdDefault:sender
{
	char tmp[128];
	sprintf(tmp,"%u",threshold);
	NXWriteDefault([NXApp appName],SPEED,tmp);
	return self;
}
- writeColorDefault:sender
{
	char tmp[128];
	sprintf(tmp,"%s",(useColor)?"YES":"NO");
	NXWriteDefault([NXApp appName],USECOLOR,tmp);
	return self;
}

- writeFileDefault:sender
{
	char tmp[128];
	sprintf(tmp,"%d",fortuneFile);
	NXWriteDefault([NXApp appName],F_FILE,tmp);
	return self;
}


- writeBackingDefault:sender
{
	char tmp[128];
	sprintf(tmp,"%d",gray);
	NXWriteDefault([NXApp appName],BACKING,tmp);
	return self;
}


- setThreshold:sender
{
	threshold = [sender intValue];
	[self perform:@selector(writeThresholdDefault:)
		with:self
		afterDelay:1000
		cancelPrevious:YES];
	return self;
}

- setSpeedSlider:sender;
{
	speedSlider = sender;
	[sender setIntValue:threshold];
	return self;
}


- loadFonts;
{
	fontNames = [[FontManager new] availableFonts];
	if (fontNames) {
		for(numFonts = 0;fontNames[numFonts];numFonts++);
	}
	return self;
}


- loadWords:(const char *)database;
{
	char buf[MAXPATHLEN];
	
	if (fortuneStream) {
		NXCloseMemory(fortuneStream,NX_FREEBUFFER);
	} 
	sprintf(buf,"%s/db/%s",[appowner moduleDirectory:"Fortune"],database);
	fortuneStream = NXMapFile(buf,NX_READONLY);
	if (fortuneStream) {
		NXGetMemoryBuffer(fortuneStream,&words,&len,&maxlen);
	} else {
		NXRunAlertPanel("File not found.","I couldn't locate the fortune database "
				"'%s'",NULL,NULL,"OK",buf);
	}
	return self;
}

- (BOOL)isExcluded:(const char *)fontName
{
	int i,count = numFonts;
	for(i=0;i<count;i++){
		if (strcmp(fontName,fontNames[i])==0) {
			if (excluded[i]==EXCLUDED) {
				return YES;
			} else {
				break;
			}
		}
	}
	return NO;
}

- prepFont
{
	float size = SIZE;
	int fn;
	fn = ((unsigned)random()) % numFonts;

	while([self isExcluded:fontNames[fn]]){
		fn++;
		if (!fontNames[fn]) fn=0;
	}
	fontNum = fn;
	font = [Font newFont:fontNames[fn] size:size matrix:NX_FLIPPEDMATRIX];
	return self;
}

- drawSelf:(const NXRect *)r :(int)c
{
	if (!r || !c || c==2) return self;

	NXSetColor(NX_COLORBLACK);
	NXRectFill(r);
	[self doDrawing];
	return self;
}

- prepColor;
{
	if (useColor) {
		blue = ((float)((unsigned)random()))/(float)INT_MAX;
		red = ((float)((unsigned)random()))/(float)INT_MAX;
		green = ((float)((unsigned)random()))/(float)INT_MAX;
	} else {
		blue = red = green = (((unsigned)random())%3) / 3.0 + (1.0/3.0);
	}
	currentColor = NXConvertRGBToColor(red,blue,green);
	return self;
}


- prepPosition;
{
	xpos = (((unsigned)random()) % (unsigned)((maxCoord.x-MINX))) +MINX;
	ypos = (((unsigned)random()) % (unsigned)((maxCoord.y-MINY))) +MINY;
	return self;
}

- prepWord;
{
	unsigned i = ((unsigned)random()) % (maxlen-1);
	int j;
	numWords = 0;
	do {	
		i--;
		for(i=(i)?i:i-1;(words[i]!='\n' && i);i--);
	} while(i && words[i-1]!='%');
	for(j=0,i++;i<maxlen && !(words[i]=='\n' && words[i+1]=='%');i++,j++) {
		if (j>=maxWordLen) {
			if (!maxWordLen) maxWordLen = 256;
			currentWord = realloc(currentWord,maxWordLen*2);
			maxWordLen*=2;
		}
		if (fortuneFile == OFFENSIVE) {
			currentWord[j]=ROT(words[i]);
		} else {
			currentWord[j]=words[i];
		}
		if (currentWord[j]==' ' || currentWord[j]=='\t'){
			numWords++;
		}
	}
	currentWord[j]='\0';
	return self;
}

- prep;
{
	[self prepFont];
	[self prepColor];
	[self prepPosition];
	[self prepWord];
	return self;
}

- setGray:sender
{
	gray = [sender state];
	[fe setDrawBacking:gray];
	[self perform:@selector(writeBackingDefault:)
		with:self
		afterDelay:1000
		cancelPrevious:YES];
	[self lockFocus];
	[self doDrawing];
	[self unlockFocus];
	return self;
}

- useColor:sender;
{
	useColor = [sender state];
	[self perform:@selector(writeColorDefault:) with:self afterDelay:1000 cancelPrevious:YES];
	return self;
}

- setHighlightSwitch:sender
{
	highlightSwitch = sender;
	[highlightSwitch setState:gray];
	return self;
}

static void do_size(void)
{
#ifdef TEST1
        system("ps -u | grep BackSpace.app | grep -v grep | awk ' { print \"Size:     V=\"$5\", R=\"$6 } '");
#endif
}
       
- setOffensiveMatrix:sender;
{
	offensiveMatrix = sender;
	[offensiveMatrix selectCellWithTag:fortuneFile];
	return self;
}

- doDrawing;
{
	static const char * oldCurrent = 0;
	NXRect dr = bounds;

	do_size();
	[self prep];
	[window disableFlushWindow];
		[fe setStringValue:""];
		[fe display];
		dr = bounds;
		dr.size.width-=10.0;
		[fe setFrame:&dr];
	[window disableDisplay];
		[fe setTextColor:currentColor];
		while(1){
			int sz = [font pointSize];
			if (sz<=2) {
				/*
				 * problem in NeXT's stuff it seems. Too many
				 * tabs in the TextFields will make the width
				 * impossible to calculate. If this happens,
				 * then we just try to do our best.
				 */
				
				font = [Font newFont:fontNames[fontNum] size:8 matrix:[font matrix]];
				[fe setFrame:&bounds];
				[fe setFont:font];
#ifdef TEST2
				printf("%s\n",currentWord);
#endif
				break;
			}
#ifdef TESTING
			[fe getFrame:&dr];
			printf("Original font: (%s,%d) \t: {{%.0f,%.0f},{%.0f,%.0f}}\n",
			       [font familyName],sz,dr.origin.x,dr.origin.y,dr.size.width,dr.size.height);
#endif
			[fe setFont:font];
			[fe setStringValueNoCopy:currentWord];
			[fe sizeToFit];
			[fe getFrame:&dr];
#ifdef TESTING
			printf("Sized to fit ================\t: {{%.0f,%.0f},{%.0f,%.0f}}\n",
			       dr.origin.x,dr.origin.y,dr.size.width,dr.size.height);
#endif
			if (dr.size.width>(bounds.size.width-10.0)) {
				font = [Font newFont:fontNames[fontNum] size:sz-2 matrix:[font matrix]];
			} else if (dr.size.height>=bounds.size.height) {
				font = [Font newFont:fontNames[fontNum] size:sz-2 matrix:[font matrix]];
			} else {
				break;
			}
		}
	[window reenableDisplay];
		[fe moveTo:(bounds.size.width-dr.size.width)/2.0
			  :(bounds.size.height-dr.size.height)/2.0];
		[fe display];
	[[window reenableFlushWindow] flushWindow];
	oldCurrent = currentWord;
	return self;
}

- oneStep
{
	static unsigned lastTime = 0;
	unsigned thisTime = currentTimeInMs();
	
	if (thisTime-lastTime<(threshold*numWords)) return self;
	lastTime = thisTime;
	if (!appowner) return self;
	[window makeFirstResponder:fe];
	[self doDrawing];
	return self;
}


- (BOOL)useBufferedWindow
{
	return YES;
}

- allocTextObject;
{
	id tf = [[ThreeField alloc] initFrame:&frame];
	[tf allocateGState];
	[tf setClipping:NO];
	[tf setBezeled:NO];
	[tf setBordered:NO];
//	[tf setBackgroundGray:NX_BLACK];
	[tf setBackgroundTransparent:YES];
	[tf setAutodisplay:NO];
	[tf setEditable:NO];
	[tf setSelectable:YES];
	[self addSubview:tf];
	return tf;
}


- setup;
{
	char buf[MAXPATHLEN];
	appowner = BSThinker();
	sprintf(buf,"%s/FortuneView.nib",[appowner moduleDirectory:"Fortune"]);
	[NXApp loadNibFile:buf owner:self withNames:NO];
	[self loadWords:fortunes[fortuneFile]];
	[speedSlider setIntValue:threshold];
	[highlightSwitch setState:gray];
	fe = [self allocTextObject];
	[fe setDrawBacking:gray];
	[colorSwitch setState:useColor];
	[availableBrowser setCellClass:[GrayCell class]];
	return self;
}

#define STRDUP(a) ((a)?(strcpy(malloc((strlen(a)+1)*sizeof(char)),a)):0)

- writeExcludedDefault:sender
{
	int i,length,count = numFonts;
	char * def;
	char * out;
	for(length=i=0;i<count;i++){
		if (excluded[i] == EXCLUDED){
			length += (strlen(fontNames[i])+4);
		}
	}
	def = malloc((length+1) * sizeof(char));
	*def='\0';
	for(out=def,length=i=0;i<count;i++){
		if (excluded[i] == EXCLUDED){
			const char * current = fontNames[i];
			length = strlen(current);
			strcat(out,current);
			if (i<(count-1)) strcat(out,"; ");
			out+=length+2;
		}
	}
	NXWriteDefault([NXApp appName],EXFONTS,def);
	free(def);
	return self;
}

- showFonts:sender;
{
	[fontWindow makeKeyAndOrderFront:self];
	return self;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)col;
{
	int i;
	[matrix renewRows:0 cols:0];
	/*
	 * I've had some trouble doing this through the browser.
	 */
	[[matrix setPrototype:[[GrayCell alloc] init]] free];
	for(i=0; fontNames[i]; i++){
		id cell;
		[matrix addRow];
		cell = [matrix cellAt:i:0];
		[cell setStringValueNoCopy:fontNames[i]];
		[cell setGrayed:[self isExcluded:fontNames[i]]];
		[cell setLoaded:YES];
		[cell setLeaf:YES];
	}
	return i;
}

- setAvailableBrowser:sender;
{
	availableBrowser = sender;
	[sender setTarget:self];
	[sender setDoubleAction:@selector(toggleFont:)];
	return self;
}

- toggleFont:sender;
{
	id matrix = [availableBrowser matrixInColumn:0];
	id cell = [matrix cellAt:[matrix selectedRow]:0];
	if ([self isExcluded:[cell stringValue]]){
		[self removeFont:self];
	} else {
		[self addFont:self];
	}
	return self;
}

/*
 * ADD to the excluded list!
 */
- addFont:sender;
{
	id matrix = [availableBrowser matrixInColumn:0];
	id cell = [matrix cellAt:[matrix selectedRow]:0];

	int i,count;
	for(count=i=0;i<numFonts;i++) count+=(excluded[i]==INCLUDED);
	if (count==1) {
		NXRunAlertPanel("Hey!", "You have to keep at least one font around!",0,0,0);
	} else {
		excluded[[matrix selectedRow]]=EXCLUDED;
		[[availableBrowser window] disableFlushWindow];
		[cell setGrayed:YES];
		[availableBrowser display];
		[[[availableBrowser window] reenableFlushWindow] flushWindow];
		[self perform:@selector(writeExcludedDefault:) with:self afterDelay:1000 cancelPrevious:YES];
	}
	return self;
}

/*
 * REMOVE from the excluded list!
 */
- removeFont:sender;
{
	int col = [availableBrowser selectedColumn];
	if (col==0) {
		id matrix = [availableBrowser matrixInColumn:col];
		int row = [matrix  selectedRow]; 
		if (row>=0){
			excluded[row]=INCLUDED;
			[self perform:@selector(writeExcludedDefault:) with:self afterDelay:1000 cancelPrevious:YES];
			[[availableBrowser window] disableFlushWindow];
			[[matrix cellAt:row:0] setGrayed:NO];
			[availableBrowser display];
			[[[availableBrowser window] reenableFlushWindow] flushWindow];
		}
	}
	return self;
}

- excludeFont:(const char *)p;
{
	int i;
	for(i=0;fontNames[i];i++){
		if (strcmp(p,fontNames[i])==0){
			excluded[i]=EXCLUDED;
			break;
		}
	}
	return self;
}
		
- setExcludedFonts:(const char *)list;
{
	char array[strlen(list)+1];
	char * p;
	int i;

	excluded = (char *)malloc(sizeof(char)*numFonts);
	for(i=0;i<numFonts;i++) excluded[i]=INCLUDED;
	strcpy(array,list);
	for(i=0,p=array;;i++){
		if (array[i]==';' || array[i]=='\0') {
			int j;
			BOOL shouldBreak = (array[i]=='\0');
			array[i]='\0';
			for(j=i-1;(array[j]=='\t' || array[j]==' ');j--) array[j]='\0';
			[self excludeFont:p];
			if (shouldBreak) break;
			p=array+i+1;
			while(*p==' ' || *p=='\t') p++;
		}
	}
	return self;
}
			
- initFrame:(const NXRect *)frameRect
{
	const char *t;
	[super initFrame:frameRect];
	[self allocateGState];		// For faster lock/unlockFocus
	[self setClipping:NO];// even faster...

	[self loadFonts];
	[self newViewSize];
	srandom(time(0));
	if (t = NXReadDefault([NXApp appName],SPEED)){
		threshold = atoi(t);
	} else {
		threshold = 200;
	}
	if (t= NXReadDefault([NXApp appName],BACKING)){
		gray = atoi(t);
	} else {
		gray = PLAIN;
	}
	if (t= NXReadDefault([NXApp appName],USECOLOR)){
		useColor = (strcmp(t,"YES")==0);
	} else {
		useColor = YES;
	}
	if (t= NXReadDefault([NXApp appName],F_FILE)){
		fortuneFile = (atoi(t));
	} else {
		fortuneFile = NORMAL;
	}
	if (t = NXReadDefault([NXApp appName],EXFONTS)){
		[self setExcludedFonts:t];
	} else {
		[self setExcludedFonts:"Symbol ; Lexi"];
	}
	[self setup];
	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];
	[self newViewSize];
	return self;
}

- newViewSize
{
	if (oldSize.width == bounds.size.width &&
			oldSize.height == bounds.size.height)
	{
		return self;
	}else{
		oldSize.width = bounds.size.width;
		oldSize.height = bounds.size.height;
	}
	maxCoord.x = bounds.size.width-150.0;
	maxCoord.y = bounds.size.height;
	if (maxCoord.x < 0) maxCoord.x = 0;
	if (maxCoord.y < 0) maxCoord.y = 0;
	[fe setFrame:&frame];
	if ([self window]){
		[self display];
	}
	return self;
}

- (const char *)windowTitle
{
	return "FortuneView";
}

- inspector:sender
{
	appowner=sender;
	if (!sharedInspectorPanel){
		[self setup];
	}
	return sharedInspectorPanel;
}

@end

